﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.IO.IsolatedStorage;
using System.Windows.Forms;

using Katamino.Model.Logic;

namespace Katamino
{
    public partial class KataminoForm : Form
    {
        private string solutionImageFolder = "";

        public KataminoForm()
        {
            InitializeComponent();
        }

        private void startButton_Click(object sender, EventArgs e)
        {
            DisableStartButton(); // prevent accidental double click

            StartCalculations();

            // do work in seperate thread
            backgroundWorker1.RunWorkerAsync();
            // invokes backgroundWorker1_DoWork

            // click function finishes, with process in background
            // form is responsive with startButton disabled
            // background worker will do work and update form via delegates:
            // backgroundWorker1_ProgressChanged
        }

        private DateTime dtStart;
        private DateTime dtEnd;

        private void StartCalculations()
        {
            // Prepare work. Setting UI variables in its own thread is thread safe
            solutionNr = 0;
            solutionsFoundLabel.Text = String.Concat("#solutions found: ", Convert.ToString(solutionNr));

            dtStart = DateTime.Now;
            tStartLabel.Text = dtStart.ToShortTimeString();

        }

        private void documentationButton_Click(object sender, EventArgs e)
        {
            DisableStartButton(); // don't allow start button on Documentation Board

            SelectDocumentationBoardInBoardSizeComboBox();

            BoardWithGraphics documentationBoard = new BoardWithGraphics(69, 46);

            KataminoProblem k = new KataminoProblem(5, 12);

            // create penta's as if for 5x12 Board.
            var b = new Board(5, 12);

            foreach (Penta p in k.Variations)
            {
                var p1 = p.Id - 1;
                var p2 = p.Variation - 1;
                var bits = p.Bits;

                for (int i = 0; bits > 0; i++, bits = bits / 2)
                {
                    if ((bits & 1) == 1)
                    {
                        documentationBoard.D[(i % 5) + p1 * 6, (i / 5) + p2 * 6] = p.Id;
                    }
                }
            }

            pictureBox1.Image = documentationBoard.Render(pictureBox1.Width, pictureBox1.Height);

            if (storeImagesCheckBox.Checked)
            {
                var imageFilename = Path.Combine(solutionImageFolder, @"katamino_variations.png");
            }
        }

        private void SelectDocumentationBoardInBoardSizeComboBox()
        {
            boardSizeComboBox.SelectedIndex = 0;
        }
        private void cancelButton_Click(object sender, EventArgs e)
        {
            // Runs on the UI thread, sets other thread's CancellationPending true
            backgroundWorker1.CancelAsync();

            // backgroundWorker1.ReportProgress(100);

        }
        private void exitButton_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }

        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            // This is executed on a sepate thread
            Debug.Assert(InvokeRequired,
                "Threading Error",
                "This thread should not run on UI thread");

            KataminoController.Calculate(this, e); // pass the Katamino form
        }

        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            Debug.Assert(!InvokeRequired,
                "Threading Error",
                "This thread should run on UI thread");

            // Raised on UI thread when DoWork calls ReportProgress
            // Progress is reported on each Katamino solution
            this.progressBar1.Value = e.ProgressPercentage; // Integer type..
            this.progressBar2.Value = (progressBar2.Value + 5) % 105;

            int pct = e.ProgressPercentage;
            if (pct < 1) pct = 1;

            completedLabel.Text = pct.ToString() + "%";

            // calculate calculation time passed and total expected.
            DateTime tNow = DateTime.Now;
            TimeSpan tElapsed = tNow.Subtract(dtStart);
            double msExpected = tElapsed.TotalMilliseconds * 100 / pct;
            if (msExpected < 1000.0) msExpected = 1000.0;

            // calculate expected end time
            dtEnd = dtStart.AddMilliseconds(msExpected);

            tEndLabel.Text = dtEnd.ToShortTimeString();

            this.solutionsFoundLabel.Text = String.Concat("#solutions found: ", Convert.ToString(solutionNr));
            this.solutionsFoundLabel.Refresh();

        }
        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            // Raised on UI thread when DoWork finishes
            Debug.Assert(!InvokeRequired,
                "Threading Error",
                "This thread should run on UI thread");

            if (e.Cancelled) { } // MessageBox.Show("Cancelled!");
            else if (e.Error != null) { } // MessageBox.Show("Error!");
            else { } // MessageBox.Show("Het resultaat is: " + e.Result);

            EnableStartButton(); // Re-enable after finishing

            // look finished wrt progress bars upon completion
            progressBar1.Value = 100;
            progressBar2.Value = 100;
        }

        // Looks simple, but refactored anyway
        private void EnableStartButton()
        {
            startButton.Enabled = true;
        }

        private void DisableStartButton()
        {
            this.startButton.Enabled = false;
        }

        public Size ImageSize()
        {
            return this.pictureBox1.Size;
        }

        public BackgroundWorker KataminoBackgroundWorker()
        {
            return this.backgroundWorker1;
        }

        // To update the picturebox on the UI thread:
        //  Check if it runs on the UI thread by !InvokeRequired
        public void UpdatePicture(BoardWithGraphics b)
        {
            if (!this.InvokeRequired)
            {
              lock (this)
              {
                  pictureBox1.Image = b.Render(pictureBox1.Width, pictureBox1.Height);

                pictureBox1.Refresh();

                solutionNr++;
                solutionsFoundLabel.Text = String.Concat("#solutions found: ", Convert.ToString(solutionNr));

                var fileName = Path.Combine(solutionImageFolder, 
                    "katamino_solution_" + solutionNr.ToString() + ".png");

                saveStrategy.SaveBitmap(b.picture, fileName);
                  // bm.Save(fileName, System.Drawing.Imaging.ImageFormat.png);
              }
            }
            else
            {
                this.Invoke(new MethodInvoker_TakesBoard(UpdatePicture), new object[] { b });
            }
        }
        //  Delegate to pass to Invoke
        public delegate void MethodInvoker_TakesBoard(BoardWithGraphics b);

        private static int solutionNr = 0;

        delegate void SetTextCallback();

        public void ReportSolution(int numberOfSolutions)
        {
            solutionNr = numberOfSolutions;
            ReportSolution();
        }


        public void ReportSolution()
        {
            string text = String.Format("Found solution number {0,5}" + Environment.NewLine,  solutionNr);

            if (this.outputTextBox.InvokeRequired)
            {
                SetTextCallback d = new SetTextCallback(ReportSolution);
                this.Invoke(d);
            }
            else
            {
                outputTextBox.Text += text;
            }
        }

        private void pictureBox1_Paint(object sender, PaintEventArgs e)
        {
            // dummy paint function, helps rendering in multi-threaded environment!
            // It executes after rendering is complete before returning control
            // to the background worker.
            // [Otherwise, the control is sometimes returned before rendering complete]
        }

        // Strategy Design Pattern: select one of those (dynamically?!)
        ISaveBitmap saveStrategy = new FileSaveStrategy();

        private void pictureBox1_Click(object sender, EventArgs e)
        {
            // TODO: Come up with something nice, like Switch Full Window

        }

        private void storeImagesCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            solutionFolderLabel.Text = "";
            if (this.storeImagesCheckBox.Checked) 
            {
                DialogResult result=this.solutionPictureFolderBrowserDialog.ShowDialog();
                if (result == DialogResult.OK)
                {
                    // the code here will be executed if the user presses Open in the dialog.
                    solutionImageFolder = Path.Combine(solutionPictureFolderBrowserDialog.SelectedPath, boardSizeComboBox.Text);
                    solutionFolderLabel.Text = "Solutions will be stored in: " + solutionImageFolder;

                }
                else
                {
                    this.storeImagesCheckBox.Checked = false;
                }
            }
        }

        KataminoProblem problem;
        public int BoardWidth;
        public int BoardHeight;

        BoardWithGraphics b;
        private void boardSizeComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            DisableStartButton();

            var s = ((ComboBox)sender).SelectedItem as string;
            switch (s) {
                case "12x5":
                    BoardWidth = 12;
                    BoardHeight = 5;
                    problem = new KataminoProblem(12, 5);
                    b = new BoardWithGraphics(12, 5);
                    EnableStartButton();
                    break;
                case "10x6":
                    BoardWidth = 10;
                    BoardHeight = 6;
                    problem = new KataminoProblem(10, 6);
                    b = new BoardWithGraphics(10, 6);
                    EnableStartButton();
                    break;
                case "15x4":
                    BoardWidth = 15;
                    BoardHeight = 4;
                    problem = new KataminoProblem(15, 4);
                    b = new BoardWithGraphics(15, 4);
                    EnableStartButton();
                    break;
                case "20x3":
                    BoardWidth = 20;
                    BoardHeight = 3;
                    problem = new KataminoProblem(20, 3);
                    b = new BoardWithGraphics(20, 3);
                    EnableStartButton();
                    break;
                case "Documentation":
                    b = new BoardWithGraphics(72, 48);
                    BoardWidth = 72;
                    BoardHeight = 48;
                    break;
                default: // TODO: alert: invalid selection of a Board. Not implemented or invalid text input?!
                    BoardWidth = 12;
                    BoardHeight = 5;
                    problem = new KataminoProblem(12, 5);
                    b = new BoardWithGraphics(12, 5);
                    s = s + " is an erroneous selection.";
                    break;
            }

            if (BoardWithGraphics.hasGraphics)
            {
                b = new BoardWithGraphics(BoardWidth, BoardHeight);
                pictureBox1.Image = b.Render(pictureBox1.Width, pictureBox1.Height);
            }
            
            boardSizeLabel.Text = "Board size: " + s;
        }

        private void showSolutionsCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            showSolutionsCheckBox.Checked |= showPartialSolutionsCheckBox.Checked;
            BoardWithGraphics.hasGraphics = showSolutionsCheckBox.Checked || storeImagesCheckBox.Checked;
        }

        private void showPartialSolutionsCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            showSolutionsCheckBox.Checked |= showPartialSolutionsCheckBox.Checked;
        }

        private void pictureBox1_Resize(object sender, EventArgs e)
        {
            // TODO: reset Board Graphics properties, recalculate sq parameter
        }

        // ISaveBitmap saveStrategy = new IsolatedStoreSaveStrategy();
        // Make this List<ISaveBitmap> ?
    }

    // Strategy Design Pattern to make it easy to select between store methods.
    // Or create a iteratable List<ISaveBitmap>, to allow multiple save strategies.
    interface ISaveBitmap
    {
        void SaveBitmap(Bitmap bm, string fileName);
    }
    class FileSaveStrategy : ISaveBitmap
    {   // pro: easy access, con: Code Access Security (will not run in guest account)
        public void SaveBitmap(Bitmap bm, string fileName)
        {
            bm.Save(fileName, System.Drawing.Imaging.ImageFormat.Png);
        }
    }
    class IsolatedStoreSaveStrategy : ISaveBitmap
    {   // pro: Code Access Security, con: Isolated Storage needs browser in app to access
        static IsolatedStorageFile machineStorage =
            IsolatedStorageFile.GetMachineStoreForAssembly();

        public void SaveBitmap(Bitmap bm, string fileName)
        {
            IsolatedStorageFileStream fileStream = new IsolatedStorageFileStream(fileName, FileMode.Create);
            bm.Save(fileStream, System.Drawing.Imaging.ImageFormat.Png);
        }
    }
}
